#pragma once

#include "Reflection/TypeUtils.h"

class CType;

//like std::any
class CAny
{
public:
    CAny()
    {
        Type=nullptr;
        ValuePtr=nullptr;
        RefCount=nullptr;
    }
    
    CAny(CType* InType)
    {
        Type=InType;
        ValuePtr=nullptr;
        RefCount=nullptr;
    }

    CAny(CType* InType,void* InValuePtr)
    {
        Type=InType;
        ValuePtr=InValuePtr;
        RefCount=new int64_t(1);
    }
    
    template<typename T>
    CAny(const T& InValue)
    {
        Type=WH::TReflectionType<std::remove_reference_t<T>>::FindOrCreateType();
        ValuePtr= Type->CloneInstance(&InValue);
        RefCount=new int64_t(1);
    }

    template<typename ObjectType>
    CAny(const TSharedObjectPtr<ObjectType>& Object)
    {
        if(Object)
        {
            Type=WH::FindOrCreateObjectPtrType(Object->GetClass());
            ValuePtr= Type->CloneInstance(&Object);
            RefCount=new int64_t(1);
        }
        else
        {
            Type=WH::TReflectionType<TSharedObjectPtr<ObjectType>>::FindOrCreateType();
            ValuePtr= Type->CloneInstance(&Object);
            RefCount=new int64_t(1);
        }
    }

    template<typename ObjectType>
    CAny(const TWeakObjectPtr<ObjectType>& Object)
    {
        if(Object)
        {
            Type=WH::FindOrCreateWeakObjectPtrType(Object->GetClass());
            ValuePtr= Type->CloneInstance(&Object);
            RefCount=new int64_t(1);
        }
        else
        {
            Type=WH::TReflectionType<TWeakObjectPtr<ObjectType>>::FindOrCreateType();
            ValuePtr= Type->CloneInstance(&Object);
            RefCount=new int64_t(1);
        }
    }

    CAny(const CAny& Other)
    {
        Type=Other.Type;
        if(Other.ValuePtr)
        {
            ValuePtr=Type->CloneInstance(Other.ValuePtr);
            RefCount=new int64_t(1);
        }
        else
        {
            ValuePtr=nullptr;
            RefCount=nullptr;
        }
    }

    CAny(CAny&& Other)
    {
        Type=Other.Type;
        ValuePtr= Other.ValuePtr;
        RefCount=Other.RefCount;

        Other.Type=nullptr;
        Other.ValuePtr=nullptr;
        Other.RefCount=nullptr;
    }


    CAny& operator =(const CAny& Other)
    {
        Reset();

        Type=Other.Type;
        if(Other.ValuePtr)
        {
            ValuePtr=Type->CloneInstance(Other.ValuePtr);
            RefCount=new int64_t(1);
        }
        else
        {
            ValuePtr=nullptr;
            RefCount=nullptr;
        }

        return *this;
    }

    CAny& operator =(CAny&& Other)
    {
        Reset();

        Type=Other.Type;
        ValuePtr= Other.ValuePtr;
        RefCount=Other.RefCount;

        Other.Type=nullptr;
        Other.ValuePtr=nullptr;
        Other.RefCount=nullptr;

        return *this;
    }

    //create a new any by cloning the value
    static CAny Clone(CType* InType, void* InValuePtr)
    {
        //clone the value
        return CAny(InType,InType->CloneInstance(InValuePtr));
    }

    //create a new any and share the value
    static CAny CreatReference(const CAny& InAny)
    {
        CAny Result;
        Result.Type=InAny.Type;
        Result.ValuePtr=InAny.ValuePtr;
        Result.RefCount=InAny.RefCount;

        *Result.RefCount+=1;

        return Result;
    }

    void* GetPointer() const
    {
        return  ValuePtr;
    }

    CType* GetType() const 
    {
        return Type;
    }

    void SetType(CType* InType)
    {
        Type=InType;
    }

    ~CAny()
    {
        Reset();

    }

    operator bool() const
    {
        return (bool)Type;
    }
protected:
    CType* Type;
    void * ValuePtr;

    //reference count of the value,
    //if the value is null, the reference count is also null
    //if the value is not null, the RefCount is not null and >0
    int64_t* RefCount;  

    void Reset()
    {   
        if(RefCount)
        {
            assert(ValuePtr && *RefCount>0);
            (*RefCount)--;

            if(*RefCount==0)
            {
                Type->EraseInstance(ValuePtr);
                free(ValuePtr);
                delete RefCount;
            }
        }
       
        ValuePtr=nullptr;
        Type=nullptr;
        RefCount=nullptr;
    }
};

template<typename T>
class AnyCastClass
{
public:    
    T operator ()(const CAny& InAny)
    {
        assert(
            InAny.GetType()->IsForceConvertableTo(WH::TReflectionType<T>::FindOrCreateType())
        );

        return *reinterpret_cast<T*>(InAny.GetPointer());        
    }
};

template<typename T>
class AnyCastClass<TSharedObjectPtr<T>>
{
public:
    TSharedObjectPtr<T> operator ()(const CAny& InAny)
    {
        if(InAny)
        {
            assert(InAny.GetType()->IsForceConvertableTo(WH::TReflectionType<TSharedObjectPtr<T>>::FindOrCreateType()));
            return *reinterpret_cast<TSharedObjectPtr<T>*>(InAny.GetPointer());   
        }
        else
        {
            return TSharedObjectPtr<T>();
        }     
    }
};
template<typename T>
class AnyCastClass<TWeakObjectPtr<T>>
{
public:
    TWeakObjectPtr<T> operator ()(const CAny& InAny)
    {
        if(InAny)
        {
            assert(InAny.GetType()->IsForceConvertableTo(WH::TReflectionType<TWeakObjectPtr<T>>::FindOrCreateType()));
            return *reinterpret_cast<TWeakObjectPtr<T>*>(InAny.GetPointer());   
        }
        else
        {
            return TWeakObjectPtr<T>();
        }     
    }
};




template<typename T>
std::remove_cvref_t<T> AnyCast(const CAny& InAny)
{
    return AnyCastClass<std::remove_cvref_t<T>>() (InAny);
}
